package ace.module.account.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.utils.SystemClockUtils;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.spring.data.jdbc.querydsl.core.util.DeleteFlagUtils;
import ace.cmp.spring.data.jdbc.querydsl.core.util.PredicateBuilder;
import ace.cmp.spring.data.jdbc.querydsl.rsql.core.RsqlQuerydslVisitor;
import ace.cmp.util.AceEnumUtils;
import ace.module.account.api.enums.IamAccountStatusEnum;
import ace.module.account.api.model.dto.IamAccountDto;
import ace.module.account.api.model.input.IamAccountFilterInput;
import ace.module.account.api.model.input.IamAccountModifyStatusInput;
import ace.module.account.api.model.input.IamAccountSearchInput;
import ace.module.account.api.model.input.IamAccountTagSearchInput;
import ace.module.account.api.model.input.IamAccountWithProfileSearchInput;
import ace.module.account.api.model.input.account.IamAdminAccountAdminSearchInput;
import ace.module.account.core.impl.dao.aggregate.IamAccountAggregate;
import ace.module.account.core.impl.dao.entity.IamAccount;
import ace.module.account.core.impl.dao.entity.QIamAccount;
import ace.module.account.core.impl.dao.entity.QIamAccountProfile;
import ace.module.account.core.impl.dao.repository.IamAccountRepository;
import ace.module.account.core.impl.manager.IamAccountManager;
import ace.module.common.core.impl.manager.impl.AbstractEntityOfAppManager;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QPageRequest;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 账户管理类.
 *
 * @author caspar
 */
@Slf4j
@AllArgsConstructor
@Component
public class IamAccountManagerImpl
    extends AbstractEntityOfAppManager<IamAccount, Long, IamAccountDto>
    implements IamAccountManager {

  private final IamAccountRepository iamAccountRepository;
  private final IamAccountAggregate iamAccountAggregate;
  private final MessageSourceAssertService assertService;

  @Override
  public Page<IamAccountDto> pageWithProfile(
      String query, Long iamAppId, Pageable pageable) {
    this.assertIsTrue(
        pageable.getPageSize() > this.getMaxPageSize(),
        this.getI18nCodePrefix() + "." + "max-page-size-limit",
        new Object[]{this.getMaxPageSize()},
        "每页显示最大数量{0}");
    this.assertIsTrue(
        pageable.getPageNumber() > this.getMaxPage(),
        this.getI18nCodePrefix() + "." + "max-page-limit",
        new Object[]{this.getMaxPage()},
        "最多显示{0}页数据,请增加过滤条件,缩小数据范围");

    QIamAccount qIamAccount = new QIamAccount("account");
    QIamAccountProfile qIamAccountProfile = new QIamAccountProfile("profile");
    Predicate predicate = null;
    if (StringUtils.isNotEmpty(query)) {
      try {
        predicate =
            this.getQuerydslParser()
                .parse(query, new RsqlQuerydslVisitor(qIamAccount, qIamAccountProfile));
      } catch (Exception ex) {

        log.warn("query查询语句解析失败", ex);
        this.assertIsTrue(
            true, this.getI18nCodePrefix() + "." + "search-query-parse-error",
            "query查询语句解析失败");
      }
    }
    if (iamAppId != null) {
      predicate = ExpressionUtils.and(predicate, qIamAccount.iamAppId.eq(iamAppId));
    }

    Predicate finalPredicate = predicate;

    Page<IamAccount> pageResult = this.getRepository().page(qIamAccount, finalPredicate, pageable);

    return pageResult.map(p -> this.getEntityToDtoConverter().convert(p));

  }

  @Override
  public Page<IamAccountDto> pageWithProfile(
      IamAccountWithProfileSearchInput input, Pageable pageable) {

    List<IamAccountTagSearchInput> tagSearchInputList = new ArrayList<>();
    if (StringUtils.isNotBlank(input.getTag())) {
      tagSearchInputList.add(
          IamAccountTagSearchInput.builder().key(input.getTag()).build()
      );
    }

    IamAccountSearchInput searchInput = IamAccountSearchInput.builder()
        .filter(
            IamAccountFilterInput.builder()
                .accountTags(tagSearchInputList)
                .accountId(input.getAccountId())
                .nickName(input.getNickName())
                .build()
        )
        .iamAppId(input.getIamAppId())
        .build();

    return this.search(searchInput, pageable);
  }

  @Override
  public Page<IamAccountDto> search(
      IamAccountSearchInput input, Pageable pageable) {
    return this.iamAccountAggregate
        .search(input, pageable)
        .map(p -> this.getEntityToDtoConverter().convert(p));
  }

  @Override
  public IamAccount getAndCheckNotNullByIdAndDeleteFlagIsFalse(Long accountId) {
    IamAccount iamAccount = this.iamAccountRepository.getByIdAndDeleteFlag(accountId,
        CoreConstant.DELETE_FLAG_NOT);

    this.checkAll(iamAccount);

    return iamAccount;
  }

  @Override
  public IamAccount getAndCheckNotNullByIdAndIamAppIdAndDeleteFlagIsFalse(Long iamAccountId, Long iamAppId) {
    IamAccount iamAccount = this.iamAccountRepository.getByIdAndIamAppIdAndDeleteFlag(iamAccountId,
        iamAppId,
        CoreConstant.DELETE_FLAG_NOT);

    this.checkAll(iamAccount);

    return iamAccount;
  }

  @Override
  public Page<IamAccount> adminSearch(Long adminIamAppId, IamAdminAccountAdminSearchInput input, Pageable pageable) {
    QIamAccount qIamAccount = QIamAccount.iamAccount;
    Predicate predicate = PredicateBuilder.of()
        .add(qIamAccount.iamAppId.eq(adminIamAppId))
        .add(qIamAccount.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT))

        .addNotEmpty(input.getId(), () -> qIamAccount.id.eq(input.getId()))
        .addNotEmpty(input.getCreateTimeStart(), () -> qIamAccount.createTime.goe(input.getCreateTimeStart()))
        .addNotEmpty(input.getCreateTimeEnd(), () -> qIamAccount.createTime.loe(input.getCreateTimeEnd()))
        .all();

    QPageRequest qPageRequest = QPageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), qIamAccount.id.desc());

    return this.getRepository().page(predicate, qPageRequest);
  }

  @Override
  public IamAccount getAndCheckById(Long id) {
    IamAccount iamAccount = this.getById(id);

    this.checkAll(iamAccount);

    return iamAccount;
  }

  private void checkAll(IamAccount iamAccount) {

    this.checkNotNull(iamAccount);

    assertService.isTrue(
        DeleteFlagUtils.isDelete(iamAccount),
        IamAccount.class.getName() + ".error-not-null",
        "数据不存在");

    assertService.isTrue(
        !IamAccountStatusEnum.NORMAL.getCode().equals(iamAccount.getStatus()),
        IamAccount.class.getName() + ".error-not-null",
        "账户已经锁定");
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void modify(IamAccount iamAccount, Integer status, Boolean passwordChangeRequired) {
    IamAccountStatusEnum statusEnum = AceEnumUtils.getEnum(IamAccountStatusEnum.class, status);
    iamAccount.setStatus(statusEnum.getCode());
    iamAccount.setPasswordChangeRequired(passwordChangeRequired);
    this.modify(iamAccount, false);
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public Long modifyStatus(List<IamAccountModifyStatusInput> inputs) {

    return this.iamAccountRepository.update(sqlUpdateClause -> {
      QIamAccount qIamAccount = QIamAccount.iamAccount;
      Long now = SystemClockUtils.currentTimeMillis();
      inputs.forEach(input -> {
        IamAccountStatusEnum statusEnum = AceEnumUtils.getEnum(IamAccountStatusEnum.class, input.getStatus());

        Long accountId = input.getAccountId();

        sqlUpdateClause
            .set(qIamAccount.status, statusEnum.getCode())
            .set(qIamAccount.updateTime, now)
            .set(qIamAccount.rowVersion, qIamAccount.rowVersion.add(1))
            .where(qIamAccount.id.eq(accountId));

        sqlUpdateClause.addBatch();
      });
      return sqlUpdateClause.execute();
    });
  }

  private void checkNotNull(IamAccount iamAccount) {
    assertService.isNull(iamAccount, IamAccount.class.getName() + ".error-not-null", "数据不存在");
  }
}
